package com.shadowtorcher.soulknife.tableManager;

import com.shadowtorcher.soulknife.SoulKnifeDB;
import com.shadowtorcher.soulknife.crud.Querier;
import com.shadowtorcher.soulknife.parser.SoulKnifeConfig;
import com.shadowtorcher.soulknife.parser.TypeConverter;
import com.shadowtorcher.soulknife.util.Logger;
import com.shadowtorcher.soulknife.util.Metadata;

import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

public class Updater {
    public static void doUpdate(Class object, int version) throws SQLException {
        String tableName = object.getSimpleName();
        List<Metadata> metadataList = SoulKnifeDB.selectWhere("mkey = ?", getVersionName(tableName)).setClass(Metadata.class);
        if (metadataList.size() == 0) {
            Metadata metadata = new Metadata();
            metadata.setMkey(getVersionName(tableName));
            metadata.setMval(String.valueOf(SoulKnifeConfig.SchemasVersion));
            metadata.save();
            return;
        }
        if (metadataList.size() == 1) {
            Metadata metadata = metadataList.get(0);
            if (Integer.parseInt(metadata.getMval()) == SoulKnifeConfig.SchemasVersion) {
                return;
            }
            if (Integer.parseInt(metadata.getMval()) >= SoulKnifeConfig.SchemasVersion) {
                Logger.logE("DataBase version is higher than Config Version stopping");
                throw new RuntimeException("DataBase version is higher than Config Version stopping");
            }
            modifyDataTable(object);
            Logger.logD("Update DataBase version to " + version +
                    " The original version is " + metadata.getMval());
            metadata.setMval(String.valueOf(version));
            metadata.update();
            return;
        }
        throw new SQLException("metadata table error");
    }

    private static void modifyDataTable(Class object) throws SQLException {
        Map<String, String> newColumnsName = getNewColumns(object);
        Map<String, String> oldColumnsName = getOldColumns(object);

        Set<String> bothHave = new HashSet<>(newColumnsName.keySet());
        bothHave.retainAll(oldColumnsName.keySet());

        for (String s : bothHave) {
            newColumnsName.remove(s);
            oldColumnsName.remove(s);
        }

        for (Map.Entry<String, String> entry : newColumnsName.entrySet()) {
            addColumns(object, entry);
        }

        for (Map.Entry<String, String> entry : oldColumnsName.entrySet()) {
            deleteColumns(object, entry);
        }
    }

    private static void deleteColumns(Class object, Map.Entry<String, String> column) throws SQLException {
        SQLManager.runSQL(
                "ALTER TABLE "
                        + object.getSimpleName() +
                        " DROP COLUMN " + column.getKey());
    }

    private static void addColumns(Class object, Map.Entry<String, String> columnName) throws SQLException {
        SQLManager.runSQL(
                "ALTER TABLE "
                        + object.getSimpleName() +
                        " ADD " + columnName.getKey() + " " +
                        TypeConverter.javaToSQL(columnName.getValue()));

    }

    private static Map<String, String> getNewColumns(Class object) {
        Map<String, String> newColumns = new HashMap<>();
        Field[] field = object.getDeclaredFields();
        for (Field afield : field) {
            newColumns.put(afield.getName(), afield.getGenericType().toString());
        }
        newColumns.put("PRIMARY_KEY", Long.class.getSimpleName());
        return newColumns;
    }

    private static Map<String, String> getOldColumns(Class object) {
        Map<String, String> oldColumns = new HashMap<>();
        try {
            ResultSet resultSet = Querier.doQuery("information_schema.COLUMNS",
                    "table_name = ? and table_schema = ?",
                    object.getSimpleName(), SoulKnifeConfig.Schemas);
            while (resultSet.next()) {
                oldColumns.put(resultSet.getString("COLUMN_NAME"), resultSet.getString("DATA_TYPE"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return oldColumns;
    }

    private static String getVersionName(String str) {
        return str + "Version";
    }
}
